/*
 * Wazuh Vulnerability Scanner - Unit Tests
 * Copyright (C) 2015, Wazuh Inc.
 * September 21, 2023.
 *
 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public
 * License (version 2) as published by the FSF - Free Software
 * Foundation.
 */

#include "osScanner_test.hpp"
#include "../../../../shared_modules/utils/flatbuffers/include/syscollector_deltas_generated.h"
#include "../../../../shared_modules/utils/flatbuffers/include/syscollector_deltas_schema.h"
#include "../../../../wazuh_modules/vulnerability_scanner/include/vulnerabilityRemediations_generated.h"
#include "json.hpp"
#include "loggerHelper.h"

/* Test constants */

namespace NSOsScannerTest
{
    const char* INCLUDE_DIRECTORIES[] = {FLATBUFFER_SCHEMAS_DIR, nullptr};
    const std::string CANDIDATES_FLATBUFFER_SCHEMA_PATH {FLATBUFFER_SCHEMAS_DIR "/vulnerabilityCandidate.fbs"};
    const std::string REMEDIATIONS_FLATBUFFER_SCHEMA_PATH {FLATBUFFER_SCHEMAS_DIR "/vulnerabilityRemediations.fbs"};

    const std::string CVE_ID_1 = "CVE-2025-1";
    const std::string CVE_ID_2 = "CVE-2025-2";

    const std::string DELTA_OS_INSERT_WINDOWS =
        R"({
            "agent_info": {
                "agent_id": "001",
                "agent_ip": "192.168.33.20",
                "agent_name": "agent"
            },
            "data_type": "dbsync_osinfo",
            "data": {
                "architecture": "x86_64",
                "hostname": "agent",
                "os_build": "7601",
                "os_major": "6",
                "os_minor": "1",
                "os_name": "Microsoft Windows 7",
                "os_release": "sp1",
                "os_version": "6.1.7601",
                "os_platform": "windows"
            },
            "operation": "INSERTED"
            })";

    const nlohmann::json OSCPE_GLOBAL =
        R"(
        {
            "Microsoft Windows 7": "microsoft:windows_7:$(RELEASE):::::"
        })"_json;

    const std::string CANDIDATES_AFFECTED_WINDOWS_CVE_ID_1 =
        R"(
            {
                "candidates": [
                    {
                        "cveId": "CVE-2025-1",
                        "defaultStatus": "unaffected",
                        "platforms": [
                            "microsoft:windows_7::::::"
                        ],
                        "versions": [
                            {
                                "status": "affected",
                                "version": "0",
                                "versionType": "custom",
                                "lessThan": "6.1.7777",
                            }
                        ]
                    }
                ]
            }
        )";

    const std::string CANDIDATES_AFFECTED_WINDOWS_CVE_ID_2 =
        R"(
            {
                "candidates": [
                    {
                        "cveId": "CVE-2025-2",
                        "defaultStatus": "unaffected",
                        "platforms": [
                            "microsoft:windows_7::::::"
                        ],
                        "versions": [
                            {
                                "status": "affected",
                                "version": "0",
                                "versionType": "custom",
                                "lessThan": "6.1.7777",
                            }
                        ]
                    }
                ]
            }
        )";

    const std::string CANDIDATES_AFFECTED_DIFFERENT_RANGE_WINDOWS_CVE_ID_1 =
        R"(
            {
                "candidates": [
                    {
                        "cveId": "CVE-2025-1",
                        "defaultStatus": "unaffected",
                        "platforms": [
                            "microsoft:windows_7::::::"
                        ],
                        "versions": [
                            {
                                "status": "affected",
                                "version": "0",
                                "versionType": "custom",
                                "lessThan": "6.0.7777",
                            }
                        ]
                    }
                ]
            }
        )";

    const nlohmann::json HOTFIXES =
        R"(
        [
            {
                "hotfix": "KB123456"
            }
        ])"_json;

    const std::string REMEDIATIONS =
        R"(
        {
            "updates": [
                "KB123456"
            ]
        }
    )";

    const std::string REMEDIATIONS_NOT_INSTALLED =
        R"(
        {
            "updates": [
                "KB123"
            ]
        }
    )";

} // namespace NSOsScannerTest
/* SetUp / TearDown */

void OsScannerTest::SetUp()
{
    m_spDatabaseFeedManagerMock = std::make_shared<MockDatabaseFeedManager>();
    m_spScanContext = std::make_shared<TrampolineScanContext>();
    spOsDataCacheMock = std::make_shared<MockOsDataCache>();
    spGlobalDataMock = std::make_shared<MockGlobalData>();
    spSocketDBWrapperMock = std::make_shared<MockSocketDBWrapper>();
}

void OsScannerTest::TearDown()
{
    m_spDatabaseFeedManagerMock.reset();
    m_spScanContext.reset();
    spOsDataCacheMock.reset();
    spGlobalDataMock.reset();
    spSocketDBWrapperMock.reset();
    m_spParser.reset();
    m_spRemediationsParser.reset();
}

/* Helpers */

void OsScannerTest::loadScanContext(std::string deltaMsg)
{
    m_spParser = std::make_shared<flatbuffers::Parser>();
    ASSERT_TRUE(m_spParser->Parse(syscollector_deltas_SCHEMA));
    ASSERT_TRUE(m_spParser->Parse(deltaMsg.c_str()));
    uint8_t* buffer = m_spParser->builder_.GetBufferPointer();
    std::variant<const SyscollectorDeltas::Delta*, const Synchronization::SyncMsg*, const nlohmann::json*>
        syscollectorDelta = SyscollectorDeltas::GetDelta(reinterpret_cast<const char*>(buffer));
    m_spScanContext = std::make_shared<TrampolineScanContext>(syscollectorDelta);
}

void OsScannerTest::parseRemediations(std::string remediationsStr,
                                      FlatbufferDataPair<NSVulnerabilityScanner::RemediationInfo>& remediationsFb)
{
    std::string remediationsFlatbufferSchemaStr;
    m_spRemediationsParser = std::make_shared<flatbuffers::Parser>();

    // Read schemas from filesystem.
    bool valid = flatbuffers::LoadFile(
        NSOsScannerTest::REMEDIATIONS_FLATBUFFER_SCHEMA_PATH.c_str(), false, &remediationsFlatbufferSchemaStr);
    ASSERT_EQ(valid, true);

    valid =
        (m_spRemediationsParser->Parse(remediationsFlatbufferSchemaStr.c_str(), NSOsScannerTest::INCLUDE_DIRECTORIES) &&
         m_spRemediationsParser->Parse(remediationsStr.c_str()));
    ASSERT_EQ(valid, true);

    remediationsFb.data =
        GetRemediationInfo(reinterpret_cast<const uint8_t*>(m_spRemediationsParser->builder_.GetBufferPointer()));
}

void OsScannerTest::scanCandidates(
    std::string candidates,
    const std::string& cnaName,
    const PackageData& package,
    const std::function<bool(const std::string& cnaName,
                             const PackageData& package,
                             const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
{
    std::string candidatesFlatbufferSchemaStr;

    // Read schemas from filesystem.
    bool valid = flatbuffers::LoadFile(
        NSOsScannerTest::CANDIDATES_FLATBUFFER_SCHEMA_PATH.c_str(), false, &candidatesFlatbufferSchemaStr);
    ASSERT_EQ(valid, true);

    // Parse schemas and JSON example.
    flatbuffers::Parser fbParser;
    valid = (fbParser.Parse(candidatesFlatbufferSchemaStr.c_str(), NSOsScannerTest::INCLUDE_DIRECTORIES) &&
             fbParser.Parse(candidates.c_str()));
    ASSERT_EQ(valid, true);

    auto candidatesArray =
        GetScanVulnerabilityCandidateArray(reinterpret_cast<const uint8_t*>(fbParser.builder_.GetBufferPointer()));

    if (candidatesArray)
    {
        for (const auto& candidate : *candidatesArray->candidates())
        {
            if (callback(cnaName, package, *candidate))
            {
                // If the candidate is vulnerable, we stop looking for.
                break;
            }
        }
    }
}

/* Tests */

/*
 * @brief Test instantiation of the OsScanner class.
 */
TEST_F(OsScannerTest, TestInstantiationOfTheOsScannerClass)
{
    TOsScanner<MockDatabaseFeedManager, TrampolineScanContext, TrampolineGlobalData, TrampolineSocketDBWrapper>
        osScanner(m_spDatabaseFeedManagerMock);
}

/*
 * @brief Test handleRequest of the OsScanner class.
 */
TEST_F(OsScannerTest, TestHandleRequest)
{
    TOsScanner<MockDatabaseFeedManager, TrampolineScanContext, TrampolineGlobalData, TrampolineSocketDBWrapper>
        osScanner(m_spDatabaseFeedManagerMock);

    EXPECT_CALL(*spGlobalDataMock, osCpeMaps()).WillRepeatedly(testing::ReturnRef(NSOsScannerTest::OSCPE_GLOBAL));
    const auto vendorMaps = R"({})"_json;
    EXPECT_CALL(*spGlobalDataMock, vendorMaps()).WillRepeatedly(testing::ReturnRef(vendorMaps));

    EXPECT_CALL(*spOsDataCacheMock, setOsData(_, _)).Times(1);
    loadScanContext(NSOsScannerTest::DELTA_OS_INSERT_WINDOWS);

    const auto testCandidates = NSOsScannerTest::CANDIDATES_AFFECTED_WINDOWS_CVE_ID_1;

    auto mockGetVulnerabilitiesCandidates =
        [&](const std::string& cnaName,
            const PackageData& package,
            const std::function<bool(const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
    {
        OsScannerTest::scanCandidates(testCandidates, cnaName, package, callback);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilitiesCandidates("nvd", _, _))
        .WillOnce(testing::Invoke(mockGetVulnerabilitiesCandidates));

    // Remediations

    auto mockSocketDBWrapperQuery = [&](const std::string& query, nlohmann::json& response)
    {
        response = NSOsScannerTest::HOTFIXES;
    };

    EXPECT_CALL(*spSocketDBWrapperMock, query(_, _)).Times(1).WillOnce(testing::Invoke(mockSocketDBWrapperQuery));

    auto mockGetVulnerabilityRemediation =
        [&](const std::string& cveId, FlatbufferDataPair<NSVulnerabilityScanner::RemediationInfo>& remediations)
    {
        parseRemediations(NSOsScannerTest::REMEDIATIONS_NOT_INSTALLED, remediations);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilityRemediation(NSOsScannerTest::CVE_ID_1, _))
        .Times(1)
        .WillOnce(testing::Invoke(mockGetVulnerabilityRemediation));

    EXPECT_NO_THROW(osScanner.handleRequest(m_spScanContext));

    // Expect

    EXPECT_EQ(m_spScanContext->m_elements.size(), 1);
    EXPECT_EQ(m_spScanContext->m_matchConditions.size(), 1);
    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.size(), 1);

    EXPECT_TRUE(m_spScanContext->m_elements.find(NSOsScannerTest::CVE_ID_1) != m_spScanContext->m_elements.end());
    EXPECT_TRUE(m_spScanContext->m_matchConditions.find(NSOsScannerTest::CVE_ID_1) !=
                m_spScanContext->m_matchConditions.end());
    EXPECT_TRUE(m_spScanContext->m_cnaDetectionSource.find(NSOsScannerTest::CVE_ID_1) !=
                m_spScanContext->m_cnaDetectionSource.end());

    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_1].version, "6.1.7777");
    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_1].condition, MatchRuleCondition::LessThan);

    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.at(NSOsScannerTest::CVE_ID_1), "nvd");
}

/*
 * @brief Test handleRequest of the OsScanner class with remediations.
 */
TEST_F(OsScannerTest, TestHandleRequestWithRemediations)
{
    TOsScanner<MockDatabaseFeedManager, TrampolineScanContext, TrampolineGlobalData, TrampolineSocketDBWrapper>
        osScanner(m_spDatabaseFeedManagerMock);

    EXPECT_CALL(*spGlobalDataMock, osCpeMaps()).WillRepeatedly(testing::ReturnRef(NSOsScannerTest::OSCPE_GLOBAL));
    const auto vendorMaps = R"({})"_json;
    EXPECT_CALL(*spGlobalDataMock, vendorMaps()).WillRepeatedly(testing::ReturnRef(vendorMaps));

    EXPECT_CALL(*spOsDataCacheMock, setOsData(_, _)).Times(1);
    loadScanContext(NSOsScannerTest::DELTA_OS_INSERT_WINDOWS);

    const auto testCandidates = NSOsScannerTest::CANDIDATES_AFFECTED_WINDOWS_CVE_ID_1;

    auto mockGetVulnerabilitiesCandidates =
        [&](const std::string& cnaName,
            const PackageData& package,
            const std::function<bool(const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
    {
        OsScannerTest::scanCandidates(testCandidates, cnaName, package, callback);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilitiesCandidates("nvd", _, _))
        .WillOnce(testing::Invoke(mockGetVulnerabilitiesCandidates));

    // Remediations

    auto mockSocketDBWrapperQuery = [&](const std::string& query, nlohmann::json& response)
    {
        response = NSOsScannerTest::HOTFIXES;
    };

    EXPECT_CALL(*spSocketDBWrapperMock, query(_, _)).Times(1).WillOnce(testing::Invoke(mockSocketDBWrapperQuery));

    auto mockGetVulnerabilityRemediation =
        [&](const std::string& cveId, FlatbufferDataPair<NSVulnerabilityScanner::RemediationInfo>& remediations)
    {
        parseRemediations(NSOsScannerTest::REMEDIATIONS, remediations);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilityRemediation(NSOsScannerTest::CVE_ID_1, _))
        .Times(1)
        .WillOnce(testing::Invoke(mockGetVulnerabilityRemediation));

    EXPECT_NO_THROW(osScanner.handleRequest(m_spScanContext));

    // Expect

    EXPECT_EQ(m_spScanContext->m_elements.size(), 0);
    EXPECT_EQ(m_spScanContext->m_matchConditions.size(), 0);
    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.size(), 0);
}

/*
 * @brief Test cna default array priority with only one CNA.
 */
TEST_F(OsScannerTest, TestCnaArrayPriorityOneCna)
{
    TOsScanner<MockDatabaseFeedManager, TrampolineScanContext, TrampolineGlobalData, TrampolineSocketDBWrapper>
        osScanner(m_spDatabaseFeedManagerMock);

    EXPECT_CALL(*spGlobalDataMock, osCpeMaps()).WillRepeatedly(testing::ReturnRef(NSOsScannerTest::OSCPE_GLOBAL));
    const auto vendorMaps = R"({"adp_default_array":["nvd"]})"_json;
    EXPECT_CALL(*spGlobalDataMock, vendorMaps()).WillRepeatedly(testing::ReturnRef(vendorMaps));

    EXPECT_CALL(*spOsDataCacheMock, setOsData(_, _)).Times(1);
    loadScanContext(NSOsScannerTest::DELTA_OS_INSERT_WINDOWS);

    const auto testCandidates = NSOsScannerTest::CANDIDATES_AFFECTED_WINDOWS_CVE_ID_1;

    auto mockGetVulnerabilitiesCandidates =
        [&](const std::string& cnaName,
            const PackageData& package,
            const std::function<bool(const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
    {
        OsScannerTest::scanCandidates(testCandidates, cnaName, package, callback);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilitiesCandidates("nvd", _, _))
        .WillOnce(testing::Invoke(mockGetVulnerabilitiesCandidates));

    // Remediations

    auto mockSocketDBWrapperQuery = [&](const std::string& query, nlohmann::json& response)
    {
        response = NSOsScannerTest::HOTFIXES;
    };

    EXPECT_CALL(*spSocketDBWrapperMock, query(_, _)).Times(1).WillOnce(testing::Invoke(mockSocketDBWrapperQuery));

    auto mockGetVulnerabilityRemediation =
        [&](const std::string& cveId, FlatbufferDataPair<NSVulnerabilityScanner::RemediationInfo>& remediations)
    {
        parseRemediations(NSOsScannerTest::REMEDIATIONS_NOT_INSTALLED, remediations);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilityRemediation(NSOsScannerTest::CVE_ID_1, _))
        .Times(1)
        .WillOnce(testing::Invoke(mockGetVulnerabilityRemediation));

    EXPECT_NO_THROW(osScanner.handleRequest(m_spScanContext));

    EXPECT_EQ(m_spScanContext->m_elements.size(), 1);
    EXPECT_EQ(m_spScanContext->m_matchConditions.size(), 1);
    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.size(), 1);

    EXPECT_TRUE(m_spScanContext->m_elements.find(NSOsScannerTest::CVE_ID_1) != m_spScanContext->m_elements.end());
    EXPECT_TRUE(m_spScanContext->m_matchConditions.find(NSOsScannerTest::CVE_ID_1) !=
                m_spScanContext->m_matchConditions.end());
    EXPECT_TRUE(m_spScanContext->m_cnaDetectionSource.find(NSOsScannerTest::CVE_ID_1) !=
                m_spScanContext->m_cnaDetectionSource.end());

    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_1].version, "6.1.7777");
    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_1].condition, MatchRuleCondition::LessThan);

    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.at(NSOsScannerTest::CVE_ID_1), "nvd");
}

/*
 * @brief Test cna default array priority with the first CNA as priority in the detection.
 */
TEST_F(OsScannerTest, TestCnaArrayPriorityMultipleCnaFirstPriority)
{
    TOsScanner<MockDatabaseFeedManager, TrampolineScanContext, TrampolineGlobalData, TrampolineSocketDBWrapper>
        osScanner(m_spDatabaseFeedManagerMock);

    EXPECT_CALL(*spGlobalDataMock, osCpeMaps()).WillRepeatedly(testing::ReturnRef(NSOsScannerTest::OSCPE_GLOBAL));
    const auto vendorMaps = R"({"adp_default_array":["cisa","nvd"]})"_json;
    EXPECT_CALL(*spGlobalDataMock, vendorMaps()).WillRepeatedly(testing::ReturnRef(vendorMaps));

    EXPECT_CALL(*spOsDataCacheMock, setOsData(_, _)).Times(1);
    loadScanContext(NSOsScannerTest::DELTA_OS_INSERT_WINDOWS);

    // First CNA
    const auto testCandidatesFirstCna = NSOsScannerTest::CANDIDATES_AFFECTED_WINDOWS_CVE_ID_1;

    auto mockGetVulnerabilitiesCandidatesFirstCna =
        [&](const std::string& cnaName,
            const PackageData& package,
            const std::function<bool(const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
    {
        OsScannerTest::scanCandidates(testCandidatesFirstCna, cnaName, package, callback);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilitiesCandidates("cisa", _, _))
        .WillOnce(testing::Invoke(mockGetVulnerabilitiesCandidatesFirstCna));

    // Second CNA
    const auto testCandidates = NSOsScannerTest::CANDIDATES_AFFECTED_WINDOWS_CVE_ID_1;

    auto mockGetVulnerabilitiesCandidates =
        [&](const std::string& cnaName,
            const PackageData& package,
            const std::function<bool(const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
    {
        OsScannerTest::scanCandidates(testCandidates, cnaName, package, callback);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilitiesCandidates("nvd", _, _))
        .WillOnce(testing::Invoke(mockGetVulnerabilitiesCandidates));

    // Remediations

    auto mockSocketDBWrapperQuery = [&](const std::string& query, nlohmann::json& response)
    {
        response = NSOsScannerTest::HOTFIXES;
    };

    EXPECT_CALL(*spSocketDBWrapperMock, query(_, _)).Times(1).WillOnce(testing::Invoke(mockSocketDBWrapperQuery));

    auto mockGetVulnerabilityRemediation =
        [&](const std::string& cveId, FlatbufferDataPair<NSVulnerabilityScanner::RemediationInfo>& remediations)
    {
        parseRemediations(NSOsScannerTest::REMEDIATIONS_NOT_INSTALLED, remediations);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilityRemediation(NSOsScannerTest::CVE_ID_1, _))
        .Times(1)
        .WillOnce(testing::Invoke(mockGetVulnerabilityRemediation));

    EXPECT_NO_THROW(osScanner.handleRequest(m_spScanContext));

    // Expect

    EXPECT_EQ(m_spScanContext->m_elements.size(), 1);
    EXPECT_EQ(m_spScanContext->m_matchConditions.size(), 1);
    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.size(), 1);

    EXPECT_TRUE(m_spScanContext->m_elements.find(NSOsScannerTest::CVE_ID_1) != m_spScanContext->m_elements.end());
    EXPECT_TRUE(m_spScanContext->m_matchConditions.find(NSOsScannerTest::CVE_ID_1) !=
                m_spScanContext->m_matchConditions.end());
    EXPECT_TRUE(m_spScanContext->m_cnaDetectionSource.find(NSOsScannerTest::CVE_ID_1) !=
                m_spScanContext->m_cnaDetectionSource.end());

    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_1].version, "6.1.7777");
    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_1].condition, MatchRuleCondition::LessThan);

    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.at(NSOsScannerTest::CVE_ID_1), "cisa");
}

/*
 * @brief Test cna default array priority with the first CNA as priority in the detection but only detection in the
 * second.
 */
TEST_F(OsScannerTest, TestCnaArrayPriorityMultipleCnaFirstPriorityDetectionSecond)
{
    TOsScanner<MockDatabaseFeedManager, TrampolineScanContext, TrampolineGlobalData, TrampolineSocketDBWrapper>
        osScanner(m_spDatabaseFeedManagerMock);

    EXPECT_CALL(*spGlobalDataMock, osCpeMaps()).WillRepeatedly(testing::ReturnRef(NSOsScannerTest::OSCPE_GLOBAL));
    const auto vendorMaps = R"({"adp_default_array":["cisa","nvd"]})"_json;
    EXPECT_CALL(*spGlobalDataMock, vendorMaps()).WillRepeatedly(testing::ReturnRef(vendorMaps));

    EXPECT_CALL(*spOsDataCacheMock, setOsData(_, _)).Times(1);
    loadScanContext(NSOsScannerTest::DELTA_OS_INSERT_WINDOWS);

    // First CNA
    const auto testCandidatesFirstCna = NSOsScannerTest::CANDIDATES_AFFECTED_DIFFERENT_RANGE_WINDOWS_CVE_ID_1;

    auto mockGetVulnerabilitiesCandidatesFirstCna =
        [&](const std::string& cnaName,
            const PackageData& package,
            const std::function<bool(const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
    {
        OsScannerTest::scanCandidates(testCandidatesFirstCna, cnaName, package, callback);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilitiesCandidates("cisa", _, _))
        .WillOnce(testing::Invoke(mockGetVulnerabilitiesCandidatesFirstCna));

    // Second CNA
    const auto testCandidates = NSOsScannerTest::CANDIDATES_AFFECTED_WINDOWS_CVE_ID_1;

    auto mockGetVulnerabilitiesCandidates =
        [&](const std::string& cnaName,
            const PackageData& package,
            const std::function<bool(const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
    {
        OsScannerTest::scanCandidates(testCandidates, cnaName, package, callback);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilitiesCandidates("nvd", _, _))
        .WillOnce(testing::Invoke(mockGetVulnerabilitiesCandidates));

    // Remediations

    auto mockSocketDBWrapperQuery = [&](const std::string& query, nlohmann::json& response)
    {
        response = NSOsScannerTest::HOTFIXES;
    };

    EXPECT_CALL(*spSocketDBWrapperMock, query(_, _)).Times(1).WillOnce(testing::Invoke(mockSocketDBWrapperQuery));

    auto mockGetVulnerabilityRemediation =
        [&](const std::string& cveId, FlatbufferDataPair<NSVulnerabilityScanner::RemediationInfo>& remediations)
    {
        parseRemediations(NSOsScannerTest::REMEDIATIONS_NOT_INSTALLED, remediations);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilityRemediation(NSOsScannerTest::CVE_ID_1, _))
        .Times(1)
        .WillOnce(testing::Invoke(mockGetVulnerabilityRemediation));

    EXPECT_NO_THROW(osScanner.handleRequest(m_spScanContext));

    // Expect

    EXPECT_EQ(m_spScanContext->m_elements.size(), 1);
    EXPECT_EQ(m_spScanContext->m_matchConditions.size(), 1);
    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.size(), 1);

    EXPECT_TRUE(m_spScanContext->m_elements.find(NSOsScannerTest::CVE_ID_1) != m_spScanContext->m_elements.end());
    EXPECT_TRUE(m_spScanContext->m_matchConditions.find(NSOsScannerTest::CVE_ID_1) !=
                m_spScanContext->m_matchConditions.end());
    EXPECT_TRUE(m_spScanContext->m_cnaDetectionSource.find(NSOsScannerTest::CVE_ID_1) !=
                m_spScanContext->m_cnaDetectionSource.end());

    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_1].version, "6.1.7777");
    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_1].condition, MatchRuleCondition::LessThan);

    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.at(NSOsScannerTest::CVE_ID_1), "nvd");
}

/*
 * @brief Test cna default array priority with detections in both CNAs.
 */
TEST_F(OsScannerTest, TestCnaArrayPriorityMultipleCnaDetectionBoth)
{
    TOsScanner<MockDatabaseFeedManager, TrampolineScanContext, TrampolineGlobalData, TrampolineSocketDBWrapper>
        osScanner(m_spDatabaseFeedManagerMock);

    EXPECT_CALL(*spGlobalDataMock, osCpeMaps()).WillRepeatedly(testing::ReturnRef(NSOsScannerTest::OSCPE_GLOBAL));
    const auto vendorMaps = R"({"adp_default_array":["cisa","nvd"]})"_json;
    EXPECT_CALL(*spGlobalDataMock, vendorMaps()).WillRepeatedly(testing::ReturnRef(vendorMaps));

    EXPECT_CALL(*spOsDataCacheMock, setOsData(_, _)).Times(1);
    loadScanContext(NSOsScannerTest::DELTA_OS_INSERT_WINDOWS);

    // First CNA
    const auto testCandidatesFirstCna = NSOsScannerTest::CANDIDATES_AFFECTED_WINDOWS_CVE_ID_2;

    auto mockGetVulnerabilitiesCandidatesFirstCna =
        [&](const std::string& cnaName,
            const PackageData& package,
            const std::function<bool(const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
    {
        OsScannerTest::scanCandidates(testCandidatesFirstCna, cnaName, package, callback);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilitiesCandidates("cisa", _, _))
        .WillOnce(testing::Invoke(mockGetVulnerabilitiesCandidatesFirstCna));

    // Second CNA
    const auto testCandidates = NSOsScannerTest::CANDIDATES_AFFECTED_WINDOWS_CVE_ID_1;

    auto mockGetVulnerabilitiesCandidates =
        [&](const std::string& cnaName,
            const PackageData& package,
            const std::function<bool(const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
    {
        OsScannerTest::scanCandidates(testCandidates, cnaName, package, callback);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilitiesCandidates("nvd", _, _))
        .WillOnce(testing::Invoke(mockGetVulnerabilitiesCandidates));

    // Remediations

    auto mockSocketDBWrapperQuery = [&](const std::string& query, nlohmann::json& response)
    {
        response = NSOsScannerTest::HOTFIXES;
    };

    EXPECT_CALL(*spSocketDBWrapperMock, query(_, _)).Times(1).WillOnce(testing::Invoke(mockSocketDBWrapperQuery));

    auto mockGetVulnerabilityRemediation =
        [&](const std::string& cveId, FlatbufferDataPair<NSVulnerabilityScanner::RemediationInfo>& remediations)
    {
        parseRemediations(NSOsScannerTest::REMEDIATIONS_NOT_INSTALLED, remediations);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilityRemediation(NSOsScannerTest::CVE_ID_1, _))
        .Times(1)
        .WillOnce(testing::Invoke(mockGetVulnerabilityRemediation));

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilityRemediation(NSOsScannerTest::CVE_ID_2, _))
        .Times(1)
        .WillOnce(testing::Invoke(mockGetVulnerabilityRemediation));

    EXPECT_NO_THROW(osScanner.handleRequest(m_spScanContext));

    // Expect

    EXPECT_EQ(m_spScanContext->m_elements.size(), 2);
    EXPECT_EQ(m_spScanContext->m_matchConditions.size(), 2);
    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.size(), 2);

    EXPECT_TRUE(m_spScanContext->m_elements.find(NSOsScannerTest::CVE_ID_1) != m_spScanContext->m_elements.end());
    EXPECT_TRUE(m_spScanContext->m_matchConditions.find(NSOsScannerTest::CVE_ID_1) !=
                m_spScanContext->m_matchConditions.end());
    EXPECT_TRUE(m_spScanContext->m_cnaDetectionSource.find(NSOsScannerTest::CVE_ID_1) !=
                m_spScanContext->m_cnaDetectionSource.end());
    EXPECT_TRUE(m_spScanContext->m_elements.find(NSOsScannerTest::CVE_ID_2) != m_spScanContext->m_elements.end());
    EXPECT_TRUE(m_spScanContext->m_matchConditions.find(NSOsScannerTest::CVE_ID_2) !=
                m_spScanContext->m_matchConditions.end());
    EXPECT_TRUE(m_spScanContext->m_cnaDetectionSource.find(NSOsScannerTest::CVE_ID_2) !=
                m_spScanContext->m_cnaDetectionSource.end());

    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_1].version, "6.1.7777");
    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_1].condition, MatchRuleCondition::LessThan);

    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.at(NSOsScannerTest::CVE_ID_1), "nvd");

    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_2].version, "6.1.7777");
    EXPECT_EQ(m_spScanContext->m_matchConditions[NSOsScannerTest::CVE_ID_2].condition, MatchRuleCondition::LessThan);

    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.at(NSOsScannerTest::CVE_ID_2), "cisa");
}

/*
 * @brief Test cna default array priority with detections in both CNAs and remediations.
 */
TEST_F(OsScannerTest, TestCnaArrayPriorityMultipleCnaDetectionBothRemediations)
{
    TOsScanner<MockDatabaseFeedManager, TrampolineScanContext, TrampolineGlobalData, TrampolineSocketDBWrapper>
        osScanner(m_spDatabaseFeedManagerMock);

    EXPECT_CALL(*spGlobalDataMock, osCpeMaps()).WillRepeatedly(testing::ReturnRef(NSOsScannerTest::OSCPE_GLOBAL));
    const auto vendorMaps = R"({"adp_default_array":["cisa","nvd"]})"_json;
    EXPECT_CALL(*spGlobalDataMock, vendorMaps()).WillRepeatedly(testing::ReturnRef(vendorMaps));

    EXPECT_CALL(*spOsDataCacheMock, setOsData(_, _)).Times(1);
    loadScanContext(NSOsScannerTest::DELTA_OS_INSERT_WINDOWS);

    // First CNA
    const auto testCandidatesFirstCna = NSOsScannerTest::CANDIDATES_AFFECTED_WINDOWS_CVE_ID_2;

    auto mockGetVulnerabilitiesCandidatesFirstCna =
        [&](const std::string& cnaName,
            const PackageData& package,
            const std::function<bool(const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
    {
        OsScannerTest::scanCandidates(testCandidatesFirstCna, cnaName, package, callback);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilitiesCandidates("cisa", _, _))
        .WillOnce(testing::Invoke(mockGetVulnerabilitiesCandidatesFirstCna));

    // Second CNA
    const auto testCandidates = NSOsScannerTest::CANDIDATES_AFFECTED_WINDOWS_CVE_ID_1;

    auto mockGetVulnerabilitiesCandidates =
        [&](const std::string& cnaName,
            const PackageData& package,
            const std::function<bool(const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& callback)
    {
        OsScannerTest::scanCandidates(testCandidates, cnaName, package, callback);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilitiesCandidates("nvd", _, _))
        .WillOnce(testing::Invoke(mockGetVulnerabilitiesCandidates));

    // Remediations

    auto mockSocketDBWrapperQuery = [&](const std::string& query, nlohmann::json& response)
    {
        response = NSOsScannerTest::HOTFIXES;
    };

    EXPECT_CALL(*spSocketDBWrapperMock, query(_, _)).Times(1).WillOnce(testing::Invoke(mockSocketDBWrapperQuery));

    auto mockGetVulnerabilityRemediation =
        [&](const std::string& cveId, FlatbufferDataPair<NSVulnerabilityScanner::RemediationInfo>& remediations)
    {
        parseRemediations(NSOsScannerTest::REMEDIATIONS, remediations);
    };

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilityRemediation(NSOsScannerTest::CVE_ID_1, _))
        .Times(1)
        .WillOnce(testing::Invoke(mockGetVulnerabilityRemediation));

    EXPECT_CALL(*m_spDatabaseFeedManagerMock, getVulnerabilityRemediation(NSOsScannerTest::CVE_ID_2, _))
        .Times(1)
        .WillOnce(testing::Invoke(mockGetVulnerabilityRemediation));

    EXPECT_NO_THROW(osScanner.handleRequest(m_spScanContext));

    // Expect

    EXPECT_EQ(m_spScanContext->m_elements.size(), 0);
    EXPECT_EQ(m_spScanContext->m_matchConditions.size(), 0);
    EXPECT_EQ(m_spScanContext->m_cnaDetectionSource.size(), 0);
}
